/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.rultax.neural.feedforward;

import com.rultax.exception.NeuralNetworkError;
import com.rultax.matrix.Matrix;
import com.rultax.matrix.MatrixMath;
import com.rultax.neural.activation.ActivationFunction;
import com.rultax.neural.activation.ActivationSigmoid;
import java.io.Serializable;

/**
 *
 * @author Scott
 */
public class FeedForwardLayer implements Serializable{
    
    private double fire[];
    private Matrix matrix;
    private FeedForwardLayer next;
    private FeedForwardLayer previous;
    private final ActivationFunction activationFunction;
    
    public FeedForwardLayer(final ActivationFunction thresholdFunction,
            final int neuronCount){
        this.fire = new double[neuronCount];
        this.activationFunction = thresholdFunction;
    }
    
    public FeedForwardLayer(final int neuronCount){
        this(new ActivationSigmoid(), neuronCount);
    }
    
    public FeedForwardLayer cloneStructure(){
        return new FeedForwardLayer(this.activationFunction, this.getNeuronCount());
    }
    
    public double[] computeOutputs(final double pattern[]){
        if(pattern != null)
            for(int i = 0; i < getNeuronCount(); i++)
                setFire(i, pattern[i]);
        
        final Matrix inputMatrix = createInputMatrix(this.fire);
        
        for (int i = 0; i < this.next.getNeuronCount(); i++) {
            final Matrix col = this.matrix.getCol(i);
            final double sum = MatrixMath.dotProduct(col, inputMatrix);
            
            this.next.setFire(i, this.activationFunction.activationFunction(sum));
        }
        
        return this.fire;
    }
    
    private Matrix createInputMatrix(final double[] pattern){
        final Matrix result = new Matrix(1, pattern.length + 1);
        for (int i = 0; i < pattern.length; i++) {
            result.set(0, i, pattern[i]);
        }
        
        result.set(0, pattern.length, 1);
        
        return result;
    }
    
    public double[] getFire(){
        return this.fire;
    }
    
    public double getFire(final int idx){
        return this.fire[idx];
    }
    
    public Matrix getMatrix(){
        return this.matrix;
    }
    
    public int getMatrixSize(){
        if(this.matrix == null)
            return 0;
        else
            return this.matrix.size(); 
    }
    
    public int getNeuronCount(){
        return this.fire.length;
    }
    
    public FeedForwardLayer getNext(){
        return this.next;
    }
    
    public FeedForwardLayer getPrevious(){
        return this.previous;
    }
    
    public boolean hasMatrix(){
        return this.matrix != null;
    }
    
    public boolean isHidden(){
        return ((this.next != null) && (this.previous != null));
    }
    
    public boolean isInput(){
        return this.previous == null;
    }
    
    public boolean isOutput(){
        return this.next == null;
    }
    
    public void prune(final int neuron){
        if(this.matrix != null)
            setMatrix(MatrixMath.deleteRow(this.matrix, neuron));
        
        final FeedForwardLayer previous = this.getPrevious();
        if(previous != null && previous.getMatrix() != null)
            previous.setMatrix(MatrixMath.deleteCol(previous.getMatrix(), neuron));
           
    }
    
    public void reset(){
        if(this.matrix != null)
            this.matrix.randomize(-1, 1);
    }
    
    public void setFire(final int i, final double f){
        this.fire[i] = f;
    }
    
    public void setMatrix(final Matrix matrix){
        if(matrix.getRows() < 2)
            throw new NeuralNetworkError("Weight matrix includes threshold values, and must have at least 2 rows.");
        
        if(matrix != null)
            this.fire = new double[matrix.getRows() - 1];
        
        this.matrix = matrix;
    }
    
    public void setNext(final FeedForwardLayer next){
        this.next = next;
        
        this.matrix = new Matrix(this.getNeuronCount() + 1, next.getNeuronCount());
    }
    
    public void setPrevious(final FeedForwardLayer previous){
        this.previous = previous;
    }
    
    @Override
    public String toString(){
        final StringBuilder result = new StringBuilder();
        result.append("[FeedForwardLayer{Neuron Count=");
        result.append(getNeuronCount());
        result.append("}]");
        return result.toString();
    }
    
    public ActivationFunction getActivationFunction(){
        return this.activationFunction;
    }
}
